import sys
import sqlite3
import threading
import time

import requests
import tkinter as tk
from tkinter import ttk, messagebox
from datetime import datetime, timedelta
from threading import Thread, Event, Lock
from time import sleep
from math import ceil
from PIL import Image, ImageTk
from io import BytesIO

import B_Login


# ======================
# 数据库模块
# ======================
class DatabaseManager:
    def __init__(self, db_name='bilibili_mall.db'):
        self.conn = sqlite3.connect(db_name,check_same_thread=False)
        self.lock = Lock()  # 新增数据库操作锁
        self._create_table()

    def _create_table(self):
        """创建商品数据表，设置商品名为唯一键"""
        cursor = self.conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS products (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT UNIQUE,      -- 商品名称（唯一）
                price INTEGER,         -- 价格（单位：分）
                image_url TEXT,        -- 图片链接
                item_url TEXT,         -- 商品详情页链接
                update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        self.conn.commit()

    def upsert_product(self, name, price, image_url, item_url):
        """
        插入或更新商品数据
        如果商品已存在且新价格更低，则更新记录
        """
        cursor = self.conn.cursor()
        try:
            # 使用SQL的UPSERT功能
            cursor.execute('''
                INSERT INTO products (name, price, image_url, item_url)
                VALUES (?, ?, ?, ?)
                ON CONFLICT(name) DO UPDATE SET
                    price = excluded.price,
                    image_url = excluded.image_url,
                    item_url = excluded.item_url,
                    update_time = CURRENT_TIMESTAMP
                WHERE excluded.price < price
            ''', (name, price, image_url, item_url))
            self.conn.commit()
            return cursor.rowcount  # 返回受影响的行数
        except sqlite3.Error as e:
            print(f"数据库操作失败: {str(e)}")
            return 0
    def verify_products(self):
        """新增：验证商品链接有效性"""
        cursor = self.conn.cursor()
        cursor.execute('SELECT item_url, id FROM products')
        products = cursor.fetchall()

        invalid_ids = []
        for url, pid in products:
            try:
                response = requests.get(url, timeout=5)
                if "下架原因" in response.text:
                    print(f"商品 {url} 已下架")
                    invalid_ids.append(pid)
            except Exception as e:
                print(f"验证失败 {url}: {str(e)}")

        if invalid_ids:
            print(f"删除 {len(invalid_ids)} 个无效商品")
            placeholders = ','.join(['?'] * len(invalid_ids))
            cursor.execute(f'DELETE FROM products WHERE id IN ({placeholders})', invalid_ids)
            self.conn.commit()
            return len(invalid_ids)
        return 0


# ======================
# 爬虫模块
# ======================
class BilibiliCrawler:
    def __init__(self, db_manager, update_ui_callback=None,initial_filters=None):
        self.db = db_manager
        self.stop_event = Event()
        self.current_count = 0
        self.update_ui = update_ui_callback

        helper = B_Login.BilibiliHelper()

        try:
            if helper.init_driver():
                if helper.login():
                    self.cookies = helper.get_market_cookies()
                    print("获取到的Cookies:", self.cookies)
                else:
                    print("登录失败")
            else:
                print("浏览器初始化失败")
        except Exception as e:
            print(f"主流程异常: {str(e)}")
        finally:
            helper.close_driver()  # 测试时可注释以保持浏览器打开
            pass

        # 初始化会话对象
        self.session = requests.Session()
        self.session.headers.update({
            'authority': 'mall.bilibili.com',
            'accept': 'application/json, text/plain, */*',
            'accept-language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
            'content-type': 'application/json',
            'origin': 'https://mall.bilibili.com',
            'referer': 'https://mall.bilibili.com/neul-next/index.html?page=magic-market_index',
            'sec-ch-ua': '"Not/A)Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Windows"',
            'sec-fetch-dest': 'empty',
            'sec-fetch-mode': 'cors',
            'sec-fetch-site': 'same-origin',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
        })
        self.filter_params = initial_filters or {
            'priceFilters': [],
            'discountFilters': [],
            'categoryFilter': ""  # 修改键名从category -> categoryFilter
        }
        # 验证参数格式

    def update_filters(self, new_filters):
        """线程安全的参数更新"""
        with threading.Lock():
            self.filter_params = {
                'priceFilters': new_filters.get('priceFilters', []),
                'discountFilters': new_filters.get('discountFilters', []),
                'categoryFilter': new_filters.get('categoryFilter', "")
            }
            self._validate_filters()

    def update_filters(self, filters):
        """更新筛选参数"""
        self.filter_params = {
            'price_range': filters['priceFilters'],
            'discount_range': filters['discountFilters'],
            'category': filters['categoryFilter']
        }

    def _make_request(self, payload):
        """发送请求并处理响应"""
        url = "https://mall.bilibili.com/mall-magic-c/internet/c2c/v2/list"
        try:
            response = self.session.post(
                url,
                json=payload,
                cookies=self.cookies,
                headers={'x-csrf-token': self.cookies.get('bili_jct', '')},
                timeout=10
            )
            response.raise_for_status()
            return response.json()
        except Exception as e:
            print(f"请求失败: {str(e)}")
            return None

    def crawl_products(self, search_keyword=None):
        """修改后的爬取逻辑"""
        next_id = None
        self.current_count = 0
        print('==================================================================================================')
        print('[INFO] 爬虫线程循环中...')
        while not self.stop_event.is_set():
            payload = {
                "categoryFilter": self.filter_params['categoryFilter'],  # 修改键名
                "sortType": "PRICE_ASC",
                "priceFilters": self.filter_params['priceFilters'],     # 修改键名
                "discountFilters": self.filter_params['discountFilters'], # 修改键名
                "nextId": next_id
            }

            '''
            categoryFilter = 2312 = 手办
            categoryFilter = 2066 = 模型
            categoryFilter = 2066 = 周边
            categoryFilter = 2273 = 3C
            categoryFilter = fudai_cate_id = 福袋

            sortType = PRICE_DESC = 降序排列
            sortType = PRICE_ASC = 降序排列

            priceFilters "0-2000" = 0-20元的价格筛选范围
            priceFilters "2000-3000" = 20-30元的价格筛选范围
            依此类推...

            discountFilters "0-30" = 0-3折的折扣范围
            discountFilters "30-50" = 3-5折的折扣范围
            依次类推...
            '''

            data = self._make_request(payload)
            if not data or data.get('code') != 0:
                break

            products = data.get('data', {}).get('data', [])
            next_id = data.get('data', {}).get('nextId')

            for item in products:
                if self.stop_event.is_set():
                    print('[INFO] 爬虫线程已停止。')
                    return

                # 提取商品信息
                product_name = item.get('c2cItemsName', '')
                product_id = item.get('c2cItemsId', '')
                detail = item.get('detailDtoList', [{}])[0]
                price = item.get('showPrice', 0)
                image_url = "https:" + detail.get('img', '') if detail.get('img') else ''
                item_url = f"https://mall.bilibili.com/neul-next/index.html?page=magic-market_detail&noTitleBar=1&itemsId={product_id}&from=market_index"
                print(f"商品：{product_name}|价格：{price}元\n",
                      f"商品链接：{item_url}")

                # 入库处理
                affected = self.db.upsert_product(product_name, price, image_url, item_url)
                if affected > 0:
                    self.current_count += 1
                    if self.update_ui:
                        self.update_ui(self.current_count)
                print('[SUCCESS] 入库成功')
                print('==================================================================================================')

            if not next_id:
                break
            sleep(3)  # 礼貌性(XBZZ,WRNFQL)延迟


class FilterConfig:
    # 价格筛选配置（单位：分）
    PRICE_OPTIONS = [
        ("全选", []),
        ("0-20元", ["0-2000"]),
        ("20-30元", ["2000-3000"]),
        ("30-50元", ["3000-5000"]),
        ("50-100元", ["5000-10000"]),
        ("100-200元", ["10000-20000"]),
        ("200元以上", ["20000-0"]),
    ]

    # 折扣筛选配置（单位：百分比）
    DISCOUNT_OPTIONS = [
        ("全选", []),
        ("0-3折", ["0-30"]),
        ("3-5折", ["30-50"]),
        ("5-7折", ["50-70"]),
        ("7-原价", ["70-100"]),
    ]

    # 商品类别配置
    CATEGORY_OPTIONS = [
        ("全部分类", ""),
        ("手办", "2312"),
        ("模型", "2066"),
        ("周边", "2331"),
        ("数码3C","2273"),
        ("福袋","fudai_cate_id")
    ]

# ======================
# GUI界面模块
# ======================
class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("B站魔力市集监控系统 v2.0")
        screenwidth = self.winfo_screenwidth()  # 获取显示屏宽度
        screenheight = self.winfo_screenheight()  # 获取显示屏高度
        width = 1400
        height = 900
        size = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)  # 设置窗口居中参数
        self.geometry(size)
        self.db = DatabaseManager()
        self.crawler = None
        self.current_page = 1
        self.total_pages = 0
        self.current_data = []
        self._setup_ui()

        self.remaining_time = 0  # 新增倒计时变量
        self._enhance_ui()

    def _enhance_ui(self):
        """新增界面元素"""
        # 在原有控制面板基础上增加元素
        control_frame = self.children['!frame']

        # 新增翻页按钮
        ttk.Button(control_frame, text="上一页", command=lambda: self.change_page(-1)).grid(row=1, column=3, padx=5)
        ttk.Button(control_frame, text="下一页", command=lambda: self.change_page(1)).grid(row=1, column=4, padx=5)

        # 新增筛选下拉列表
        self._add_filters(control_frame)

        # 新增数据库验证开关
        self.verify_var = tk.BooleanVar()
        ttk.Checkbutton(control_frame, text="自动验证", variable=self.verify_var).grid(row=1, column=5, padx=5)

        # 新增倒计时标签
        self.timer_label = ttk.Label(control_frame, text="剩余时间: --:--:--")
        self.timer_label.grid(row=1, column=6, padx=5)

    def _add_filters(self, parent):
        """重构后的筛选器组件"""
        # 价格筛选
        price_frame = ttk.LabelFrame(parent, text="价格范围")
        price_frame.grid(row=1, column=0, pady=5, padx=5)
        self.price_var = tk.StringVar()

        # 生成带单位的显示文本
        price_display = [opt[0] for opt in FilterConfig.PRICE_OPTIONS]
        self.price_combobox = ttk.Combobox(
            price_frame,
            values=price_display,
            textvariable=self.price_var,
            state="readonly"
        )
        self.price_combobox.current(0)
        self.price_combobox.pack(padx=5, pady=2)

        # 折扣筛选
        discount_frame = ttk.LabelFrame(parent, text="折扣力度")
        discount_frame.grid(row=1, column=1, pady=5, padx=5)
        self.discount_var = tk.StringVar()

        discount_display = [opt[0] for opt in FilterConfig.DISCOUNT_OPTIONS]
        self.discount_combobox = ttk.Combobox(
            discount_frame,
            values=discount_display,
            textvariable=self.discount_var,
            state="readonly"
        )
        self.discount_combobox.current(0)
        self.discount_combobox.pack(padx=5, pady=2)

        # 商品类别
        category_frame = ttk.LabelFrame(parent, text="商品类别")
        category_frame.grid(row=1, column=2, pady=5, padx=5)
        self.category_var = tk.StringVar()

        category_display = [opt[0] for opt in FilterConfig.CATEGORY_OPTIONS]
        self.category_combobox = ttk.Combobox(
            category_frame,
            values=category_display,
            textvariable=self.category_var,
            state="readonly"
        )
        self.category_combobox.current(0)
        self.category_combobox.pack(padx=5, pady=2)

    def _get_selected_filters(self):
        """增强型参数转换"""
        # 价格筛选处理
        price_selected = self.price_var.get()
        price_values = []
        for opt in FilterConfig.PRICE_OPTIONS:
            if opt[0] == price_selected:
                if price_selected == "全选":
                    # 合并所有价格选项（排除全选）
                    price_values = [item[1][0] for item in FilterConfig.PRICE_OPTIONS[1:]]
                else:
                    price_values = opt[1]
                break

        # 折扣筛选处理
        discount_selected = self.discount_var.get()
        discount_values = []
        for opt in FilterConfig.DISCOUNT_OPTIONS:
            if opt[0] == discount_selected:
                if discount_selected == "全选":
                    # 合并所有折扣选项（排除全选）
                    discount_values = [item[1][0] for item in FilterConfig.DISCOUNT_OPTIONS[1:]]
                else:
                    discount_values = opt[1]
                break

        # 商品分类处理
        category_selected = self.category_var.get()
        category_value = next(
            (opt[1] for opt in FilterConfig.CATEGORY_OPTIONS if opt[0] == category_selected),
            ""
        )

        return {
            'priceFilters': price_values,
            'discountFilters': discount_values,
            'categoryFilter': category_value  # 确保键名与爬虫模块一致
        }

    def change_page(self, delta):
        """新增：翻页功能"""
        new_page = self.current_page + delta
        if 1 <= new_page <= self.total_pages:
            self.current_page = new_page
            self.show_page()

    def _update_timer(self):
        """新增：更新倒计时显示"""
        if self.remaining_time > 0:
            hours = self.remaining_time // 3600
            mins = (self.remaining_time % 3600) // 60
            secs = self.remaining_time % 60
            self.timer_label.config(text=f"剩余时间: {hours:02}:{mins:02}:{secs:02}")
            self.remaining_time -= 1
            self.after(1000, self._update_timer)
        else:
            self.timer_label.config(text="剩余时间: --:--:--")
            self.TKH.config(state='normal')
            self.TKM.config(state='normal')

    def _setup_ui(self):
        """初始化界面组件"""
        # 顶部控制面板
        control_frame = ttk.Frame(self)
        control_frame.pack(pady=10, fill='x')

        # 搜索框
        self.search_var = tk.StringVar()
        ttk.Entry(control_frame, textvariable=self.search_var, width=30).grid(row=0, column=0, padx=5)
        ttk.Button(control_frame, text="搜索", command=self.on_search).grid(row=0, column=1, padx=5)

        # 排序按钮
        ttk.Button(control_frame, text="价格升序", command=lambda: self.sort_data('asc')).grid(row=0, column=2, padx=5)
        ttk.Button(control_frame, text="价格降序", command=lambda: self.sort_data('desc')).grid(row=0, column=3, padx=5)

        # 爬虫控制
        ttk.Button(control_frame, text="开始爬取", command=self.start_crawler).grid(row=0, column=4, padx=5)
        ttk.Button(control_frame, text="停止", command=self.stop_crawler).grid(row=0, column=5, padx=5)

        # 定时设置
        ttk.Label(control_frame, text="定时:").grid(row=0, column=6)
        self.hours_var = tk.IntVar(value=24)
        self.mins_var = tk.IntVar(value=00)
        self.TKH = ttk.Spinbox(control_frame, from_=0, to=24, textvariable=self.hours_var, width=3)
        self.TKH.grid(row=0, column=7)
        ttk.Label(control_frame, text="小时").grid(row=0, column=8)
        self.TKM = ttk.Spinbox(control_frame, from_=0, to=59, textvariable=self.mins_var, width=3)
        self.TKM.grid(row=0, column=9)
        ttk.Label(control_frame, text="分钟").grid(row=0, column=10)

        # 数据显示区域
        self.canvas = tk.Canvas(self)
        self.scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = ttk.Frame(self.canvas)

        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        )
        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)

        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")

        # 分页信息
        self.page_label = ttk.Label(self, text="第1页/共0页")
        self.page_label.pack(side="bottom")

    def start_crawler(self):
        """启动爬虫线程"""
        # 禁用设置控件
        self.TKH.config(state='disabled')
        self.TKM.config(state='disabled')

        self.remaining_time = self.hours_var.get() * 3600 + self.mins_var.get() * 60
        print(f"爬取时间设置为: {self.hours_var.get()} 小时 {self.mins_var.get()} 分钟")
        self._update_timer()

        # 启动验证线程
        if self.verify_var.get():
            Thread(target=self.db.verify_products).start()

        if self.crawler and self.crawler.is_alive():
            return

        # 设置定时器
        duration = timedelta(
            hours=self.hours_var.get(),
            minutes=self.mins_var.get()
        )
        if duration.total_seconds() > 0:
            timer = Thread(target=self._run_with_timer, args=(duration,))
            timer.start()

        self.crawler = Thread(target=self._crawler_thread)
        self.crawler.start()

    def _crawler_thread(self):
        """爬虫线程"""
        # 获取筛选参数
        filters = self._get_selected_filters()

        # 正确传递参数（关键修复）
        self.crawler_instance = BilibiliCrawler(
            db_manager=self.db,
            update_ui_callback=self.update_count,
            initial_filters=filters
        )
        self.crawler_instance.stop_event.clear()
        self.crawler_instance.crawl_products()

    def update_count(self, count):
        """更新爬取计数"""
        self.after(0, lambda: self.page_label.config(text=f"已爬取商品数: {count}",font=("Arial", 15)))

    def stop_crawler(self):
        """停止爬虫"""
        self.TKH.config(state='normal')
        self.TKM.config(state='normal')
        self.remaining_time = 0
        if hasattr(self, 'crawler_instance') and self.crawler_instance:
            self.crawler_instance.stop_event.set()
            print("爬虫已停止!")



    def on_search(self):
        """处理搜索操作"""
        keyword = self.search_var.get()[:20]  # 限制20字符
        cursor = self.db.conn.cursor()
        cursor.execute('''
            SELECT name, price, image_url, item_url 
            FROM products 
            WHERE name LIKE ? 
            ORDER BY price
        ''', (f'%{keyword}%',))
        self.current_data = cursor.fetchall()
        self.total_pages = ceil(len(self.current_data) / 20)
        self.current_page = 1
        self.show_page()

    def show_page(self):
        """显示当前页数据"""
        # 清空现有内容
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()

        # 分页数据
        start = (self.current_page - 1) * 20
        end = start + 20
        paged_data = self.current_data[start:end]

        # 显示商品网格
        row, col = 0, 0
        for idx, (name, price, img_url, item_url) in enumerate(paged_data, 1):
            frame = ttk.Frame(self.scrollable_frame)
            frame.grid(row=row, column=col, padx=10, pady=10)

            # 商品图片（异步加载）
            Thread(target=self._load_image, args=(frame, img_url)).start()

            # 商品信息
            ttk.Label(frame, text=name[:30], wraplength=200).grid(row=1)
            ttk.Label(frame, text=f"¥{price}").grid(row=2)
            ttk.Button(frame, text="查看详情", command=lambda u=item_url: self.open_url(u)).grid(row=3)

            col += 1
            if col >= 5:
                col = 0
                row += 1

        # 更新分页信息
        self.page_label.config(text=f"第{self.current_page}页/共{self.total_pages}页")

    def _load_image(self, parent, url):
        """异步加载图片"""
        try:
            response = requests.get(url, timeout=10)
            img_data = Image.open(BytesIO(response.content))
            img_data.thumbnail((200, 200))
            img = ImageTk.PhotoImage(img_data)
            label = ttk.Label(parent)
            label.img = img  # 保持引用
            label.config(image=img)
            label.grid(row=0)
        except Exception as e:
            print(f"图片加载失败: {str(e)}")

    def open_url(self, url):
        """打开商品链接"""
        import webbrowser
        webbrowser.open(url)

    def sort_data(self, order='asc'):
        """排序功能"""
        self.current_data.sort(key=lambda x: x[1], reverse=(order == 'desc'))
        self.show_page()

    def _run_with_timer(self, duration):
        """定时运行"""
        sleep(duration.total_seconds())
        self.stop_crawler()


# ======================
# 运行程序
# ======================
if __name__ == "__main__":
    app = Application()
    app.mainloop()